tree grammar ValWalker;

options {
  tokenVocab = ValParser;
  ASTLabelType = CommonTree;
}

@header{
package org.concepts.java.antlr.validator;

import java.util.Map;
import java.util.HashMap;
}

@members {

 /**
  * Map of variables
  */
 Map<String,String> variables = new HashMap<String,String>(); 

 /**
  * Returns true if the String passed in 'true'. The match
  * is done in a case insensitive manner after trimming all the
  * whitespace.
  * @param data can be null.
  */
 private boolean isTrue(String data) {
   if (data != null &&
       (
         data.trim().equalsIgnoreCase("true") ||
         data.trim().equalsIgnoreCase("yes")
       ) 
       ) {
     return true;
   }
   return false;
 }

 /**
  * Parses the data and returns an integer. RuntimeException is
  * thrown if the data passed in is not a number.
  * @param data can be null. 
  */
 private int getNumber(String data) {
   try {
    return Integer.parseInt(data.trim());
   } catch(Throwable e) {
     throw new RuntimeException("Data [" + data + "]" +
     " is not a number", e);
   }
 }

 /**
  * Calls the function.
  *
  * Only two simple functions (length & matches) have been implemented
  * . Typically this method could use Reflection to let user call static
  * methods in Java classes. Support for user defined functions can also
  * be easily added.
  *
  */
 private String callFunction(String funcName, List<String> params) {
   if ("length".equalsIgnoreCase(funcName)) {
     return params.get(0).length() + "";
   } else if ("matches".equalsIgnoreCase(funcName)) {
     String data1 = params.get(0);
     String data2 = params.get(1);
     return data1.matches(data2) + ""; 
   } else {
     throw new RuntimeException(funcName + " function is not supported");
   }
 }

} // members

validator returns [boolean pass = false;]
  : ^(VARIABLES variableDeclaration*) ^(EXPR result = expr) 
  {
    pass =  isTrue(result);
  }
  ;
  
variableName returns [String variable = "";]
  : first = ALPHANUMERIC {variable += first;}
   ( second = ALPHANUMERIC {variable += second;} 
    | 
    third = SPECIALCHARACTERS {variable += third;}
   )*
  ;

value returns [String val = "";]
  : first = STRING { val += first;}
   | (
      second = ALPHANUMERIC {val += second;} 
      | 
      third = SPECIALCHARACTERS {val += third;}
     )+  
  ;

variableDeclaration
@after {
  variables.put(var, val);
}
  :  ^(VARIABLE ^(NAME var = variableName)  ^(VALUE val = expr))
  ;  
  
  
function returns [String result = "";]
@init {
  List<String> params = new ArrayList<String>(); 
}
@after {
  result = callFunction($funcName.text, params);
}
  : ^(FUNCTION 
        ^(NAME funcName = ALPHANUMERIC) 
        ^(PARAM (param = expr { params.add(param);})* ) 
     )
  ;
 
  
expr returns [String result = "true";]
  : ^(OR first = expr second = expr)
    {
        if (isTrue(first) || isTrue(second)) {
          result = "true";
        } else {
          result = "false";
        }
    }
  | ^(AND first = expr second = expr)
    {
      if (isTrue(first) && isTrue(second)) {
        result = "true";
      } else {
        result = "false";
      }
    }
  | ^(EQ first = expr second = expr)
    {
      if (first.equals(second)) {
        result = "true";
      } else {
        result = "false";
      }
    }
  | ^(NEQ first = expr second = expr)
    {
      if (!first.equals(second)) {
        result = "true";
      } else {
        result = "false";
      }
    }
  | ^(GT first = expr second = expr)
    {
      if (getNumber(first) >  getNumber(second)) {
        result = "true";
      } else {
        result = "false";
      }
    }
  | ^(LT first = expr second = expr)
    {
      if (getNumber(first) <  getNumber(second)) {
        result = "true";
      } else {
        result = "false";
      }
    }
  | ^(GTE first = expr second = expr)
    {
      if (getNumber(first) >=  getNumber(second)) {
        result = "true";
      } else {
        result = "false";
      }
    }
  | ^(LTE first = expr second = expr)
    {
      if (getNumber(first) <=  getNumber(second)) {
        result = "true";
      } else {
        result = "false";
      }
    }
  | ^(PLUS first = expr second = expr)
    {
      result  = (getNumber(first) +  getNumber(second)) + ""; 
    }
  | ^(MINUS first = expr second = expr)
    {
      result  = (getNumber(first) -  getNumber(second)) + ""; 
    }
  | ^(MULTIPLY first = expr second = expr)
    {
      result  = (getNumber(first) *  getNumber(second)) + ""; 
    }
  | ^(DIVIDE first = expr second = expr)
    {
      result  = (getNumber(first) /  getNumber(second)) + ""; 
    }
  | ^(DOLLAR first = variableName)
    {
      result  = variables.get(first);
      if (result == null) {
        throw new RuntimeException("Variable with name [" + first +
        "] not defined by the used.");
      }
    }
  | first = function
    {
      result = first;
    }
  | ALPHANUMERIC
    {
      result = $ALPHANUMERIC.text;
    }
  | STRING
    {
      result = ($STRING.text).replaceAll("\"","");
    }
  | SPECIALCHARACTERS
    {
      result = $SPECIALCHARACTERS.text;
    }
  ;
